package com.zwps.biz.domain.service.impl;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zwps.biz.api.enums.FileStoreCatalogueEnum;
import com.zwps.biz.dal.db.dao.FileRecordBizDAO;
import com.zwps.biz.dal.db.dao.FileRecordBizSettingDAO;
import com.zwps.biz.dal.db.dao.FileRecordDAO;
import com.zwps.biz.dal.db.dao.FileRecordVideoDAO;
import com.zwps.biz.dal.db.object.FileRecordBizDO;
import com.zwps.biz.dal.db.object.FileRecordBizSettingDO;
import com.zwps.biz.dal.db.object.FileRecordDO;
import com.zwps.biz.dal.db.object.FileRecordVideoDO;
import com.zwps.biz.domain.converter.FileRecordConverter;
import com.zwps.biz.domain.converter.FileRecordVideoConverter;
import com.zwps.biz.domain.enums.FileClassEnum;
import com.zwps.biz.domain.extra.rabbitmq.message.PictureCompressMessage;
import com.zwps.biz.domain.extra.rabbitmq.message.VideoProcessMessage;
import com.zwps.biz.domain.extra.rabbitmq.producer.FileProcessProducer;
import com.zwps.biz.domain.service.FileRecordBizService;
import com.zwps.biz.api.model.dto.file.record.biz.*;
import com.zwps.biz.api.model.vo.file.record.FileVO;
import com.zwps.biz.api.model.vo.file.record.biz.FileRecordBizVO;
import com.zwps.biz.api.model.vo.file.record.biz.SelectFileRecordBizPageVO;
import com.zwps.common.api.model.vo.PageInfoVO;
import com.zwps.common.core.exception.UserActionException;
import com.zwps.common.core.service.CommonService;
import com.zwps.common.minio.config.MinioConfigProperties;
import com.zwps.common.minio.service.MinioService;
import com.zwps.common.tool.DifferenceTool;
import com.zwps.common.tool.DifferenceTool.DifferenceResult;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@AllArgsConstructor
@Slf4j
public class FileRecordBizServiceImpl implements FileRecordBizService, CommonService {

    private FileRecordBizDAO fileRecordBizDAO;

    private FileRecordDAO fileRecordDAO;

    private FileProcessProducer fileProcessProducer;

    private MinioService minioService;

    private MinioConfigProperties minioConfig;

    private FileRecordBizSettingDAO fileRecordBizSettingDAO;

    private FileRecordVideoDAO fileRecordVideoDAO;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void addFileRecordBizs(AddFileRecordBizDTO dto) {
        addFileRecordBizs(dto.getBizTable(), dto.getBizId(), dto.getFileIds());
    }

    private void addFileRecordBizs(String bizTable, String bizId, Collection<String> fileIds) {
        ArrayList<FileRecordBizDO> list = new ArrayList<FileRecordBizDO>();
        String temp = FileStoreCatalogueEnum.TEMP.getCode();
        String tempPath = minioConfig.getBucketName() + "/" + temp + "/";
        ArrayList<PictureCompressMessage> pictureMessageList = new ArrayList<PictureCompressMessage>();
        ArrayList<VideoProcessMessage> videoMessageList = new ArrayList<VideoProcessMessage>();
        for (String fileId : fileIds) {
            FileRecordDO fileRecordDO = fileRecordDAO
                    .selectOne(Wrappers.lambdaQuery(FileRecordDO.class).eq(FileRecordDO::getId, fileId));
            if (fileRecordDO == null) {
                throw new UserActionException(String.format("文件ID【%s】不存在", fileId));
            }
            String filePath = fileRecordDO.getFilePath();
            if (StrUtil.isNotEmpty(filePath) && filePath.startsWith(tempPath)) {
                try {
                    String bizTablePath = minioConfig.getBucketName() + "/" + bizTable + "/";
                    String newFilePath = filePath.replace(tempPath, bizTablePath);
                    String fileUrl = minioConfig.getExternalUrl() + "/" + newFilePath;
                    LambdaUpdateWrapper<FileRecordDO> update = Wrappers.lambdaUpdate(FileRecordDO.class);
                    update.set(FileRecordDO::getFilePath, newFilePath);
                    update.set(FileRecordDO::getFileUrl, fileUrl);
                    update.eq(FileRecordDO::getId, fileRecordDO.getId());
                    if (fileRecordDAO.update(null, update) < 1) {
                        throw new UserActionException("更新移动文件后的目录失败");
                    }
                    String srcFilePath = fileRecordDO.getFilePath().substring(minioConfig.getBucketName().length());
                    String destFilePath = newFilePath.substring(minioConfig.getBucketName().length());
                    log.info("File mone srcFilePath: {}, destFilePath: {}", srcFilePath, destFilePath);
                    minioService.move(srcFilePath, destFilePath);
                } catch (Exception e) {
                    log.error("File move error: ", e);
                    throw new UserActionException("移动文件失败");
                }
            }
            if (FileClassEnum.IMAGE.getCode().equals(fileRecordDO.getFileClass())) {
                PictureCompressMessage compress = new PictureCompressMessage();
                compress.setBizTable(bizTable);
                compress.setFileId(fileId);
                compress.setRepeatStrategy(0);
                pictureMessageList.add(compress);
            } else if (FileClassEnum.VIDEO.getCode().equals(fileRecordDO.getFileClass())) {
                VideoProcessMessage compress = new VideoProcessMessage();
                compress.setBizTable(bizTable);
                compress.setFileId(fileId);
                videoMessageList.add(compress);
            }
            list.add(new FileRecordBizDO(fileId, bizTable, bizId));
        }
        if (fileRecordBizDAO.insertBatchSomeColumn(list) != fileIds.size()) {
            throw new UserActionException("新增文件关联失败");
        }
        for (VideoProcessMessage videoProcessMessage : videoMessageList) {
            fileProcessProducer.sendVideoPorcessMessage(videoProcessMessage);
        }
        for (PictureCompressMessage pictureMessage : pictureMessageList) {
            fileProcessProducer.sendPicturePorcessMessage(pictureMessage);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteFileRecordBizs(DeleteFileRecordBizDTO dto) {
        deleteFileRecordBizs(dto.getBizTable(), dto.getBizId(), dto.getFileIds());
    }

    private void deleteFileRecordBizs(String bizTable, String bizId, Collection<String> fileIds) {
        ArrayList<String> bizIds = new ArrayList<String>();
        bizIds.add(bizId);
        deleteFileRecordBizs(bizTable, bizIds, fileIds);
    }

    private void deleteFileRecordBizs(String bizTable, Collection<String> bizIds, Collection<String> fileIds) {
        LambdaQueryWrapper<FileRecordBizDO> query = Wrappers.lambdaQuery(FileRecordBizDO.class);
        query.eq(FileRecordBizDO::getBizTable, bizTable);
        query.in(FileRecordBizDO::getBizId, bizIds);
        query.in(CollectionUtil.isNotEmpty(fileIds), FileRecordBizDO::getFileId, fileIds);
        // 这里不删除文件记录考虑到2个情况
        // 1.文件记录设计一个文件支持被多次引用，这里直接删除关联记录为了避免误删，就不删除文件记录，如果文件记录真实情况是没有被引用的，会由异步删除程序处理
        // 2.情况1的文件记录如果通过异步删除程序处理可能会误删需要保留的文件，后面有相关的需求会新增过期时间字段进行处理
        fileRecordBizDAO.delete(query);
    }

    @Override
    public List<FileRecordBizVO> selectFileRecordBizs(SelectFileRecordBizDTO dto) {
        return fileRecordBizDAO.findFileRecordBizs(dto.getBizTable(), dto.getBizId());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void differUpdateFileRecordBizs(DifferUpdateFileRecordBizDTO dto) {
        if (CollectionUtil.isEmpty(dto.getFileIds())) {
            deleteFileRecordBizs(dto.getBizTable(), dto.getBizId(), null);
            return;
        }

        LambdaQueryWrapper<FileRecordBizDO> query = Wrappers.lambdaQuery(FileRecordBizDO.class);
        query.eq(FileRecordBizDO::getBizTable, dto.getBizTable());
        query.eq(FileRecordBizDO::getBizId, dto.getBizId());
        List<FileRecordBizDO> fileRecordBizs = fileRecordBizDAO.selectList(query);

        List<String> fileIdList = fileRecordBizs.stream().map((f) -> f.getFileId()).collect(Collectors.toList());
        DifferenceResult<String> diffResult = DifferenceTool.diff(dto.getFileIds(), fileIdList,
                (o1, o2) -> o1.compareTo(o2));
        if (CollectionUtil.isNotEmpty(diffResult.getIncrease())) {
            addFileRecordBizs(dto.getBizTable(), dto.getBizId(), diffResult.getIncrease());
        }

        if (CollectionUtil.isNotEmpty(diffResult.getSubtract())) {
            deleteFileRecordBizs(dto.getBizTable(), dto.getBizId(), diffResult.getSubtract());
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void batchDeleteFileRecordBizs(BatchDeleteFileRecordBizDTO dto) {
        deleteFileRecordBizs(dto.getBizTable(), dto.getBizIds(), dto.getFileIds());
    }

    @Override
    public List<FileVO> selectFiles(String bizTable, String bizId) {
        LambdaQueryWrapper<FileRecordBizDO> fileRecordBizQuery = Wrappers.lambdaQuery(FileRecordBizDO.class);
        fileRecordBizQuery.eq(FileRecordBizDO::getBizTable, bizTable);
        fileRecordBizQuery.eq(FileRecordBizDO::getBizId, bizId);
        List<FileRecordBizDO> fileRecordBizList = fileRecordBizDAO.selectList(fileRecordBizQuery);
        ArrayList<FileVO> fileList = new ArrayList<FileVO>();
        if (fileRecordBizList.isEmpty()) {
            return fileList;
        }
        List<String> fileIds = fileRecordBizList.stream().map((a) -> a.getFileId()).collect(Collectors.toList());
        LambdaQueryWrapper<FileRecordDO> fileRecordQuery = Wrappers.lambdaQuery(FileRecordDO.class);
        fileRecordQuery.eq(FileRecordDO::getDeleteStatus, 0);
        fileRecordQuery.eq(FileRecordDO::getUploadStatus, 1);
        fileRecordQuery.and((a) -> {
            a.in(FileRecordDO::getId, fileIds).or().in(FileRecordDO::getParentId, fileIds);
        });
        fileRecordQuery.orderByAsc(FileRecordDO::getParentId);
        List<FileRecordDO> fileRecordList = fileRecordDAO.selectList(fileRecordQuery);
        if (fileRecordList.isEmpty()) {
            return fileList;
        }
        HashMap<String, List<FileVO>> fileCache = new HashMap<String, List<FileVO>>();
        for (FileRecordDO fileRecord : fileRecordList) {
            FileVO fileVO = FileRecordConverter.INSTANCE.toFileVO(fileRecord);
            if (fileVO.getExternalFile() == null || fileVO.getExternalFile() == 0) {
                fileVO.setFileUrl(minioConfig.getExternalUrl() + "/" + fileVO.getFilePath());
            }
            FileRecordVideoDO fileRecordVideoDO = fileRecordVideoDAO.selectOne(new LambdaQueryWrapper<FileRecordVideoDO>()
                    .eq(FileRecordVideoDO::getFileId, fileVO.getId()));
            fileVO.setFileRecordVideoVO(FileRecordVideoConverter.INSTANCE.toFileRecordVideoVO(fileRecordVideoDO));
            if (StrUtil.isNotEmpty(fileVO.getParentId())) {
                List<FileVO> files = fileCache.get(fileVO.getParentId());
                if (files != null) {
                    files.add(fileVO);
                }
            } else {
                fileList.add(fileVO);
            }
            ArrayList<FileVO> children = new ArrayList<FileVO>();
            fileVO.setChildren(children);
            fileCache.put(fileVO.getId(), children);
        }
        return fileList;
    }

    @Override
    public List<FileRecordBizVO> selectFileRecordBiz(String fileId) {
        LambdaQueryWrapper<FileRecordBizDO> query = new LambdaQueryWrapper<>();
        query.eq(StrUtil.isNotBlank(fileId), FileRecordBizDO::getFileId, fileId);
        return FileRecordConverter.INSTANCE.toFileRecordBizVO(fileRecordBizDAO.selectList(query));
    }

    @Override
    public PageInfoVO<SelectFileRecordBizPageVO> selectFileRecordBizPage(SelectFileRecordBizPageDTO dto) {
        PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
        List<SelectFileRecordBizPageVO> list = fileRecordBizDAO.selectFileRecordBizPage(dto);

        Page<SelectFileRecordBizPageVO> page = (Page<SelectFileRecordBizPageVO>) list;
        PageInfoVO<SelectFileRecordBizPageVO> pageInfo = new PageInfoVO<SelectFileRecordBizPageVO>(page.getPageNum(),
                page.getPageSize(), page.getPages(), page.getTotal(), list);
        return pageInfo;
    }

    @Override
    public void addFileRecordBiz(AddRecordBizDTO dto) {
        LambdaQueryWrapper<FileRecordBizSettingDO> wrapper = new QueryWrapper<FileRecordBizSettingDO>().lambda();
        wrapper.eq(FileRecordBizSettingDO::getBizTable, dto.getBizTable());
        FileRecordBizSettingDO fileRecordBizSettingDO = fileRecordBizSettingDAO.selectOne(wrapper);

        FileRecordBizDO fileRecordBizDO = new FileRecordBizDO();
        fileRecordBizDO.setBizId(dto.getBizId());
        fileRecordBizDO.setBizTable(dto.getBizTable());
        fileRecordBizDO.setFileId(dto.getFileId());

        if (fileRecordBizSettingDO != null) {
            fileRecordBizDO.setBizSettingId(fileRecordBizSettingDO.getId());
        }

        if (fileRecordBizDAO.insert(fileRecordBizDO) < 1) {
            throw new UserActionException("新增文件关联失败");
        }
    }

    @Override
    public void updateFileRecordBiz(String id, UpdateRecordBizDTO dto) {
        FileRecordBizDO fileRecordBizDO = fileRecordBizDAO.selectById(id);
        if (fileRecordBizDO == null) {
            throw new UserActionException("该文件记录与业务表关联不存在");
        }

        LambdaQueryWrapper<FileRecordBizSettingDO> wrapper = new QueryWrapper<FileRecordBizSettingDO>().lambda();
        wrapper.eq(FileRecordBizSettingDO::getBizTable, dto.getBizTable());
        FileRecordBizSettingDO fileRecordBizSettingDO = fileRecordBizSettingDAO.selectOne(wrapper);

        LambdaUpdateWrapper<FileRecordBizDO> update = Wrappers.lambdaUpdate(FileRecordBizDO.class);
        update.set(change(dto.getBizTable(), fileRecordBizDO.getBizTable()), FileRecordBizDO::getBizTable,
                dto.getBizTable());
        update.set(change(dto.getFileId(), fileRecordBizDO.getFileId()), FileRecordBizDO::getFileId, dto.getFileId());
        update.set(change(dto.getBizId(), fileRecordBizDO.getBizId()), FileRecordBizDO::getBizId, dto.getBizId());

        if (fileRecordBizSettingDO != null) {
            update.set(change(dto.getBizTable(), fileRecordBizDO.getBizTable()), FileRecordBizDO::getBizTable,
                    dto.getBizTable());
        }

        if (StrUtil.isNotEmpty(update.getSqlSet())) {
            update.eq(FileRecordBizDO::getId, id);
            if (fileRecordBizDAO.update(null, update) < 1) {
                throw new UserActionException("修改文件关联失败");
            }
        }
    }

    @Override
    public void deleteFileRecordBiz(String ids) {
        List<String> idList = Arrays.asList(ids.split(","));
        int result = fileRecordBizDAO.deleteBatchIds(idList);
        if (result != idList.size()) {
            long count = fileRecordBizDAO
                    .selectCount(Wrappers.lambdaQuery(FileRecordBizDO.class).in(FileRecordBizDO::getId, idList));
            if (count > 0) {
                throw new UserActionException("删除文件关联失败");
            }
        }
    }

    @Override
    public Map<String, List<FileVO>> batchSearchFiles(String bizTable, List<String> bizIds) {

        HashMap<String, List<FileVO>> bizFileMap = Maps.newHashMap();

        LambdaQueryWrapper<FileRecordBizDO> fileRecordBizQuery = Wrappers.lambdaQuery(FileRecordBizDO.class);
        fileRecordBizQuery.eq(FileRecordBizDO::getBizTable, bizTable);
        fileRecordBizQuery.in(FileRecordBizDO::getBizId, bizIds);
        List<FileRecordBizDO> fileRecordBizList = fileRecordBizDAO.selectList(fileRecordBizQuery);
        if (fileRecordBizList.isEmpty()) {
            return bizFileMap;
        }

        List<FileVO> fileList = Lists.newArrayList();
        List<String> fileIds = fileRecordBizList.stream().map(FileRecordBizDO::getFileId).collect(Collectors.toList());
        LambdaQueryWrapper<FileRecordDO> fileRecordQuery = Wrappers.lambdaQuery(FileRecordDO.class);
        fileRecordQuery.eq(FileRecordDO::getDeleteStatus, 0);
        fileRecordQuery.eq(FileRecordDO::getUploadStatus, 1);
        fileRecordQuery.and((a) -> {
            a.in(FileRecordDO::getId, fileIds).or().in(FileRecordDO::getParentId, fileIds);
        });
        fileRecordQuery.orderByAsc(FileRecordDO::getParentId);
        List<FileRecordDO> fileRecordList = fileRecordDAO.selectList(fileRecordQuery);
        if (fileRecordList.isEmpty()) {
            return bizFileMap;
        }

        List<String> fileRecordIds = fileRecordList.stream().map(FileRecordDO::getId).distinct().collect(Collectors.toList());
        List<FileRecordVideoDO> fileRecordVideoDoList
                = fileRecordVideoDAO.selectList(new LambdaQueryWrapper<FileRecordVideoDO>().in(FileRecordVideoDO::getFileId, fileRecordIds));

        Map<String, List<FileRecordBizDO>> fileRecordBizMap = fileRecordBizList.stream().collect(Collectors.groupingBy(FileRecordBizDO::getBizId));

        fileRecordBizMap.forEach((k , v) ->{

            Map<String, List<FileVO>> fileCache = new HashMap<>();
            List<String> groupFileIds = v.stream().map(FileRecordBizDO::getFileId).collect(Collectors.toList());
            List<FileRecordDO> recordDoIdList = fileRecordList.stream().filter(fileRecordDO -> groupFileIds.contains(fileRecordDO.getId())).collect(Collectors.toList());
            List<FileRecordDO> recordDoParentList = fileRecordList.stream().filter(fileRecordDO -> groupFileIds.contains(fileRecordDO.getParentId())).collect(Collectors.toList());
            recordDoIdList.addAll(recordDoParentList);
            List<FileRecordDO> groupFileRecordList = recordDoIdList.stream().collect(
                    Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(FileRecordDO::getId))), ArrayList::new));

            List<String> groupFileRecordIds = groupFileRecordList.stream().map(FileRecordDO::getId).distinct().collect(Collectors.toList());
            Map<String, FileRecordVideoDO> fileRecordVideoDOMap =
                    fileRecordVideoDoList.stream().filter(recordDo -> groupFileRecordIds.contains(recordDo.getFileId()))
                            .collect(Collectors.toMap(FileRecordVideoDO::getFileId, Function.identity(), (k1, k2) -> k2));

            List<FileVO> fileVOList = FileRecordConverter.INSTANCE.toFileVOList(groupFileRecordList);
            for (FileVO fileVO : fileVOList) {
                if (fileVO.getExternalFile() == null || fileVO.getExternalFile() == 0) {
                    fileVO.setFileUrl(minioConfig.getExternalUrl() + "/" + fileVO.getFilePath());
                }
                FileRecordVideoDO fileRecordVideoDO = fileRecordVideoDOMap.get(fileVO.getId());
                if(fileRecordVideoDOMap.containsKey(fileVO.getId()) && Objects.nonNull(fileRecordVideoDO)){
                    fileVO.setFileRecordVideoVO(FileRecordVideoConverter.INSTANCE.toFileRecordVideoVO(fileRecordVideoDO));
                }
                if (StrUtil.isNotEmpty(fileVO.getParentId())) {
                    List<FileVO> files = fileCache.get(fileVO.getParentId());
                    if (files != null) {
                        files.add(fileVO);
                    }
                } else {
                    fileList.add(fileVO);
                }
                ArrayList<FileVO> children = new ArrayList<>();
                fileVO.setChildren(children);
                fileCache.put(fileVO.getId(), children);
            }
            bizFileMap.put(k , fileList);
        });

        return bizFileMap;
    }
}
